﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace Shared
{
    public class MultiThreadWorker<T> where T : class
    {
        int maxThread = 0;
        public int MaxThread
        {
            get { return maxThread; }
        }

        public int Count { get; set; }

        private bool CancellationPending { get; set; }

        public Queue<T> WorkingQueue { get; set; }
        private Dictionary<int, BackgroundWorker> Workers { get; set; }

        public event Action<T> RunWorkerCompleted;
        public event Action<int> ProgressChanged;
        public event Action<T> RunWorkerStarted;
        public event Action<T> OnDoWork;

        public MultiThreadWorker(int maxThread)
        {
            this.maxThread = maxThread;
            this.CancellationPending = false;

            WorkingQueue = new Queue<T>();
            Workers = new Dictionary<int, BackgroundWorker>();

            InitializeWorkers();
        }

        private void InitializeWorkers()
        {
            for (int i = 0; i < MaxThread; i++)
            {
                BackgroundWorker bw = new BackgroundWorker();
                bw.DoWork += new DoWorkEventHandler(bw_DoWork);
                bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
                bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);

                bw.WorkerReportsProgress = true;
                bw.WorkerSupportsCancellation = true;

                Workers.Add(i, bw);
            }
        }

        private int? FindNotBusyWorker()
        {
            int? result = null;
            try
            {
                foreach (var item in Workers)
                {
                    if (!item.Value.IsBusy)
                        return (item.Key);
                }

                return result;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        public bool IsThreadPoolBusy()
        {
            try
            {
                foreach (var item in Workers)
                {
                    if (item.Value.IsBusy)
                        return true;
                }

                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
        }

        public void CancelWorkers()
        {
            CancellationPending = true;

            foreach (var item in Workers)
            {
                item.Value.CancelAsync();
            }
        }

        private void DequeueWork()
        {
            int? key = null;

            lock (WorkingQueue)
            {
                do
                {
                    key = FindNotBusyWorker();

                    if (key.HasValue)
                    {
                        BackgroundWorker worker = Workers[key.Value];

                        T entry = null;
                        try
                        {
                            entry = WorkingQueue.Dequeue();
                        }
                        catch (InvalidOperationException ex)
                        {
                            Console.WriteLine(ex.Message);
                            return;
                        }

                        if (!CancellationPending)
                        {
                            worker.RunWorkerAsync(entry);
                            if (RunWorkerStarted != null)
                                RunWorkerStarted(entry);
                        }
                    }

                } while (key != null);
            }
        }

        public void EnqueueWork(T entry)
        {
            WorkingQueue.Enqueue(entry);
            Count++;
        }

        void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            DequeueWork();

            if (RunWorkerCompleted != null && e.Result != null)
                RunWorkerCompleted((T)(e.Result));

            if (ProgressChanged != null)
                ProgressChanged(100 - (WorkingQueue.Count() * 100 / Count));

        }

        void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            if (((BackgroundWorker)sender).CancellationPending)
                return;

            if (OnDoWork != null)
                OnDoWork((T)(e.Argument));

            e.Result = (T)(e.Argument);

        }

        void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //if (ProgressChanged != null)
            //    ProgressChanged(e.ProgressPercentage);
        }

        public bool Start()
        {
            if (Count == 0)
                return false;

            CancellationPending = false;
            DequeueWork();

            return true;
        }
    }
}
